package repository

import (
	"context"
	"ehsm_kms/internal/constants"
	"ehsm_kms/internal/model"
	"errors"
	"fmt"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"time"
)

var cmkFields = []string{
	"_id",
	"_rev",
	"keyid",
	"key_blob",
	"creator",
	"creation_date",
	"expire_time",
	"alias",
	"key_spec",
	"origin",
	"key_state",
}

type CmkRepository interface {
	Store(ctx context.Context,
		cmk []byte,
		appid string,
		keySpec,
		origin,
		keyUsage int,
	) error
	FindByKeyId(ctx context.Context, appid, keyId string) ([]byte, error)
	List(ctx context.Context, appid string) ([]*model.Cmk, error)
	RemoveByKeyId(ctx context.Context, appid, keyId string) error
	RemoveAll(ctx context.Context, appid string) error
	EnableKey(ctx context.Context, appid, keyId string) error
	DisableKey(ctx context.Context, appid, keyId string) error
}

func NewCmkRepository(r *Repository, log *zap.Logger) CmkRepository {
	return &cmkRepository{
		Repository: r,
		log:        log,
	}
}

type cmkRepository struct {
	*Repository
	log *zap.Logger
}

func (r *cmkRepository) Store(ctx context.Context,
	cmk []byte,
	appid string,
	keySpec,
	origin,
	keyUsage int) error {

	ct := time.Now().UnixMicro()
	keyId := uuid.New().String()
	m := model.Cmk{
		Id:           model.CmkPartitionId(keyId),
		KeyId:        keyId,
		KeyBlob:      cmk,
		Creator:      appid,
		CreationDate: ct,
		ExpireTime:   ct + constants.CMK_EFFECTIVE_DURATION,
		Alias:        "",
		KeySpec:      keySpec,
		Origin:       origin,
		KeyUsage:     keyUsage,
		KeyState:     1,
	}
	rev, err := r.db.Put(ctx, m.Id, m)
	if err != nil {
		return err
	}
	r.log.Info("create cmk", zap.String("rev", rev), zap.Object("cmk", &m))
	return nil
}

func (r *cmkRepository) FindByKeyId(ctx context.Context, appid, keyId string) ([]byte, error) {
	query := map[string]any{
		"selector": map[string]string{
			"_id": model.CmkPartitionId(keyId),
		},
		"fields": []string{"key_blob", "creator", "expire_time", "key_state"},
		"limit":  1,
	}
	rows, err := r.db.Find(ctx, query)
	if err != nil {
		return nil, err
	}
	if !rows.Next() {
		return nil, errors.New("keyid error")
	}
	var cmkM model.Cmk
	err = rows.ScanDoc(&cmkM)
	if err != nil {
		return nil, err
	}

	if cmkM.Creator != appid {
		return nil, errors.New("appid error")
	}
	if cmkM.KeyState == 0 {
		return nil, errors.New("keyid is disabled")
	}
	if time.Now().UnixMilli() > cmkM.ExpireTime {
		return nil, errors.New("keyid expire")
	}
	return cmkM.KeyBlob, nil
}

func (r *cmkRepository) List(ctx context.Context, appid string) ([]*model.Cmk, error) {
	query := map[string]any{
		"selector": map[string]string{
			"creator": appid,
		},
		"fields": []string{
			"keyid",
			"creation_date",
			"expire_time",
			"alias",
			"key_spec",
			"key_state",
		},
		"limit": 10000,
	}
	rows, err := r.db.Find(ctx, query)
	if err != nil {
		return nil, err
	}
	docs := make([]*model.Cmk, 0)
	for {
		if !rows.Next() {
			break
		}
		m := model.Cmk{}
		if err = rows.ScanDoc(&m); err != nil {
			return nil, err
		}
		docs = append(docs, &m)
	}
	return docs, nil
}

func (r *cmkRepository) RemoveByKeyId(ctx context.Context, appid, keyId string) error {
	query := map[string]any{
		"selector": map[string]string{
			"creator": appid,
			"_id":     model.CmkPartitionId(keyId),
		},
		"fields": []string{"_id", "_rev"},
		"limit":  1,
	}

	rows, err := r.db.Find(ctx, query)
	if err != nil {
		return err
	}
	if !rows.Next() {
		return errors.New("keyid failed")
	}

	m := map[string]string{}
	err = rows.ScanDoc(&m)
	if err != nil {
		return err
	}

	_, err = r.db.Delete(ctx, m["_id"], m["_rev"])
	if err != nil {
		return errors.New("deleteKey failed")
	}
	return nil
}

func (r *cmkRepository) RemoveAll(ctx context.Context, appid string) error {
	query := map[string]any{
		"selector": map[string]string{
			"creator": appid,
		},
		"fields": []string{"_id", "_rev"},
		"limit":  10000,
	}

	rows, err := r.db.Find(ctx, query)
	if err != nil {
		return err
	}

	for {
		if !rows.Next() {
			break
		}
		m := map[string]string{}
		err = rows.ScanDoc(&m)
		if err != nil {
			return err
		}
		_, _ = r.db.Delete(ctx, m["_id"], m["_rev"])
	}
	return nil
}

func (r *cmkRepository) EnableKey(ctx context.Context, appid, keyId string) error {
	query := map[string]any{
		"selector": map[string]string{
			"creator": appid,
			"_id":     model.CmkPartitionId(keyId),
		},
		"fields": cmkFields,
		"limit":  1,
	}

	rows, err := r.db.Find(ctx, query)
	if err != nil {
		return err
	}
	if !rows.Next() {
		return errors.New("keyid failed")
	}

	m := map[string]any{}
	err = rows.ScanDoc(&m)
	if err != nil {
		return err
	}

	m["key_state"] = 1
	_, err = r.db.Put(ctx, m["_id"].(string), m)

	if err != nil {
		return err
	}
	return nil
}

func (r *cmkRepository) DisableKey(ctx context.Context, appid, keyId string) error {
	query := map[string]any{
		"selector": map[string]string{
			"creator": appid,
			"_id":     fmt.Sprintf("cmk:%s", keyId),
		},
		"fields": cmkFields,
		"limit":  1,
	}

	rows, err := r.db.Find(ctx, query)
	if err != nil {
		return err
	}
	if !rows.Next() {
		return errors.New("keyid failed")
	}

	m := map[string]any{}
	err = rows.ScanDoc(&m)
	if err != nil {
		return err
	}

	m["key_state"] = 0
	_, err = r.db.Put(ctx, m["_id"].(string), m)

	if err != nil {
		return err
	}
	return nil
}
